
#include "xy_map.h"
#include <list>
#include <mutex>
#include <string>
#include <fstream>

#define SAFE_DELETE_ARRAY(p) do{if(p){delete[] p;p=nullptr;}}while(0);
#define SAFE_DELETE(p) do{if(p){delete p;p=nullptr;}}while(0);

static struct IMG_POOL{
	std::list<unsigned char*> pool;
#ifdef MULTI_THREAD
	std::recursive_mutex mtx;
#endif
	~IMG_POOL() {
		for (std::list<unsigned char*>::iterator iter = pool.begin();iter != pool.end();++iter) {
			delete[](*iter);
		}
		pool.clear();
	}
	unsigned char* alloc(void) {
#ifdef MULTI_THREAD
		std::lock_guard<std::recursive_mutex> lok(mtx);
#endif
		unsigned char* ptr = nullptr;
		if (pool.size() > 0) {
			ptr = pool.front();
			pool.pop_front();
		}else{
			ptr = new unsigned char[MAP_BLOCK_WIDTH * MAP_BLOCK_HEIGHT];
		}
		return ptr;
	}
	void free(unsigned char* p) {
#ifdef MULTI_THREAD
		std::lock_guard<std::recursive_mutex> lok(mtx);
#endif
		pool.push_back(p);
	}
}imgPool;

XY_Map::Image_Info::~Image_Info()
{
	if (ptr && isNew) {
		imgPool.free(ptr);
		ptr = nullptr;
		isNew = false;
	}
}

XY_Map::XY_Map()
{
	_isLoad = false;
	_pImage = nullptr;
	_pMaskInfo = nullptr;
	_pCell = nullptr;
	_isLoad = false;
#ifdef WIN32
	_hFileMap = NULL;
	_hFile = INVALID_HANDLE_VALUE;
#endif
}

XY_Map::~XY_Map()
{
	FreeMap();
}

bool XY_Map::LoadMap(const char* fileName)
{
	if (_isLoad) return true;
#ifdef WIN32
	_hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == _hFile)
		return false;
	DWORD hi, low;
	low = GetFileSize(_hFile, &hi);
	_hFileMap = CreateFileMapping(_hFile, NULL, PAGE_READONLY, hi, low, "Global\\xyq_map");
	if (!_hFileMap) {
		CloseHandle(_hFile);
		_hFile = INVALID_HANDLE_VALUE;
		return false;
	}
	_ptrData = MapViewOfFile(_hFileMap, FILE_MAP_READ, 0, 0, 0);
	if (!_ptrData) {
		CloseHandle(_hFileMap);
		CloseHandle(_hFile);
		_hFileMap = NULL;
		_hFile = INVALID_HANDLE_VALUE;
		return false;
	}
#endif
	if (*(uint32_t*)_ptrData != 0x4D312E30) {
		return false;
	}
	memcpy(&_info.width, (void*)((LONG)_ptrData + 4), sizeof(uint32_t) * 2);
	_info.blockWidth = MAP_BLOCK_WIDTH;
	_info.blockHeight = MAP_BLOCK_HEIGHT;
	_info.blockOfRow = _info.width / _info.blockWidth + (_info.width%_info.blockWidth ? 1 : 0);
	_info.blockOfCol = _info.height / _info.blockHeight + (_info.height%_info.blockHeight ? 1 : 0);
	_info.blockCount = _info.blockOfRow*_info.blockOfCol;
	_pImage = new uint32_t[_info.blockCount];
	_images = new Image_Info[_info.blockCount];
	LONG ptr = 12 + (LONG)_ptrData;
	for (uint32_t i = 0;i < _info.blockCount;++i) {
		_pImage[i] = ((uint32_t*)ptr)[i];
	}
	ptr += _info.blockCount * 4;
	_info.maskCount = *(uint32_t*)((LONG)_ptrData + *(LONG*)(ptr));
	uint32_t* pMask = new uint32_t[_info.maskCount];
	ptr += 8;
	for (uint32_t i = 0;i < _info.maskCount;++i) {
		pMask[i] = ((uint32_t*)ptr)[i];
	}
	_pMaskInfo = new MapMaskInfo[_info.maskCount];
	for (uint32_t i = 0;i < _info.maskCount;++i) {
		ptr = (LONG)_ptrData;
		ptr += pMask[i];
		memcpy(&_pMaskInfo[i], (void*)ptr, sizeof(uint32_t) * 4);
		int idxRow = _pMaskInfo[i].y / _info.blockHeight;
		int idxCol = _pMaskInfo[i].x / _info.blockWidth;
		_pMaskInfo[i].blockIndex = idxRow*_info.blockOfRow + idxCol;
		ptr += sizeof(uint32_t) * 5;
		uint32_t width = _pMaskInfo[i].width;
		uint32_t height = _pMaskInfo[i].height;
		_pMaskInfo[i].alpha = new uint8_t[width * height];
		uint8_t* temp = new uint8_t[(width + 3) / 4 * height];
		DecompressMask((void*)ptr, (void*)temp);
		uint32_t offset = 0;
		uint32_t pos = 0;
		for (uint32_t iy = height;iy > 0; --iy) {
			uint32_t index = 0;
			for (uint32_t ix = width;ix > 0;--ix, ++pos) {
				uint8_t value = temp[offset];
				value = value >> (index * 2);
				_pMaskInfo[i].alpha[pos] = (value & 3) ? 1 : 0;
				if (++index == 4) {
					index = 0;
					++offset;
				}
			}
			if (index) ++offset;
		}
	}
	delete[] pMask;
	uint32_t blockWCell = _info.blockWidth / 20;
	uint32_t blockHCell = _info.blockHeight / 20;
	uint32_t blockCell = blockWCell*blockHCell;
	_pCell = new uint8_t[_info.blockOfRow*_info.blockOfCol*blockCell];
	for (uint32_t col = 0;col < _info.blockOfCol;++col) {
		uint32_t index = col*_info.blockOfRow;
		for (uint32_t row = 0;row < _info.blockOfRow;++row) {
			LONG ptr = (LONG)_ptrData + _pImage[index+row];
			ptr += *(LONG*)ptr * 4 + 4;
			uint32_t flags = *(uint32_t*)ptr;
			uint32_t length = *(uint32_t*)(ptr + 4);
			ptr += 8 + length + 8;

			uint32_t pos = col*_info.blockOfRow*blockCell + row*blockWCell;
			for (uint32_t height = 0;height < blockHCell;++height) {
				memcpy(&_pCell[pos], (void*)ptr, blockWCell);
				ptr += blockWCell;
				pos += _info.blockOfRow*blockWCell;
			}
		}
	}
	_isLoad = true;
	return _isLoad;
}

void XY_Map::FreeMap(void)
{
#ifdef WIN32
	if (_ptrData) {
		UnmapViewOfFile(_ptrData);
		_ptrData = nullptr;
	}
	if (_hFileMap) {
		CloseHandle(_hFileMap);
		_hFileMap = NULL;
	}
	if (INVALID_HANDLE_VALUE != _hFile) {
		CloseHandle(_hFile);
		_hFile = INVALID_HANDLE_VALUE;
	}
#endif
	if (_isLoad) {
		SAFE_DELETE_ARRAY(_pImage);
		SAFE_DELETE_ARRAY(_pMaskInfo);
		SAFE_DELETE_ARRAY(_images);
		SAFE_DELETE_ARRAY(_pCell);
		_isLoad = false;
	}
}

bool XY_Map::IsLoad(void) const
{
	return _isLoad;
}

const XY_Map::MapInfo* XY_Map::GetInfo(void) const
{
	return &_info;
}

const void* XY_Map::GetImage(uint32_t index, uint32_t *dataSize /*= nullptr*/)
{
	if (!_isLoad) {
		return nullptr;
	}
	if (index < 0 || index >= _info.blockCount)
		return nullptr;
	if (!_images[index].ptr) {
		LONG ptr = (LONG)_ptrData + _pImage[index];
		ptr += *(LONG*)ptr * 4 + 4;
		uint32_t flags = *(uint32_t*)ptr;
		uint32_t length = *(uint32_t*)(ptr + 4);
		ptr += 8;
		if (flags != 0x4A504547) {
			_images[index].ptr = (unsigned char*)ptr;
			_images[index].size = length;
			_images[index].isNew = false;
		}
		else {
			uint32_t head = 0;
			for (;head < length;++head) {
				if (*(uint16_t*)(ptr + head) == 0xDAFF) {
					head += 2;break;
				}
			}
			unsigned char* pData = imgPool.alloc();
			memcpy(pData + 2, (void*)(ptr + 4), head - 1);
			pData[0] = 0xFF;pData[1] = 0xD8;
			const unsigned char data[] = { 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x22, 0x00, 0x3F, 0x00 };
			memcpy(pData + head - 1, data, sizeof(data));
			LONG tail = ptr + length - 2;
			LONG offset = ptr + head + 9;
			LONG offset2 = head - 1 + sizeof(data);
			for (;offset < tail; ++offset) {
				unsigned char temp = *(unsigned char*)(offset);
				pData[offset2++] = temp;
				if (temp == 0xFF) {
					pData[offset2++] = 0x00;
				}
			}
			pData[offset2++] = 0xFF;pData[offset2++] = 0xD9;
			_images[index].ptr = pData;
			_images[index].size = offset2;
			_images[index].isNew = true;
		}
	}
	if (dataSize)*dataSize = _images[index].size;
	return _images[index].ptr;
}

bool XY_Map::IsObstacle(uint32_t x, uint32_t y) const
{
	if (x >= _info.width / 20 || x <= 0) return true;
	if (y >= _info.height / 20 || y <= 0) return true;
	uint32_t pos = (_info.height / 20 - y - 1)*_info.width / 20 + x;
	return _pCell[pos] == 1;
}

bool XY_Map::IsEntry(uint32_t x, uint32_t y) const
{
	if (x >= _info.width / 20 || x <= 0) return false;
	if (y >= _info.height / 20 || y <= 0) return false;
	uint32_t pos = (_info.height / 20 - y - 1)*_info.width / 20 + x;
	return _pCell[pos] == 2;
}

void XY_Map::DecompressMask(void* in, void* out)
{
	register unsigned char *op;
	register  unsigned char *ip;
	register unsigned t;
	register  unsigned char *m_pos;

	op = (unsigned char *)out;
	ip = (unsigned char *)in;

	if (*ip > 17) {
		t = *ip++ - 17;
		if (t < 4)
			goto match_next;
		do *op++ = *ip++; while (--t > 0);
		goto first_literal_run;
	}

	while (1) {
		t = *ip++;
		if (t >= 16) goto match;
		if (t == 0) {
			while (*ip == 0) {
				t += 255;
				ip++;
			}
			t += 15 + *ip++;
		}

		*(unsigned *)op = *(unsigned *)ip;
		op += 4; ip += 4;
		if (--t > 0)
		{
			if (t >= 4)
			{
				do {
					*(unsigned *)op = *(unsigned *)ip;
					op += 4; ip += 4; t -= 4;
				} while (t >= 4);
				if (t > 0) do *op++ = *ip++; while (--t > 0);
			}
			else do *op++ = *ip++; while (--t > 0);
		}

	first_literal_run:

		t = *ip++;
		if (t >= 16)
			goto match;

		m_pos = op - 0x0801;
		m_pos -= t >> 2;
		m_pos -= *ip++ << 2;

		*op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos;

		goto match_done;

		while (1)
		{
		match:
			if (t >= 64)
			{

				m_pos = op - 1;
				m_pos -= (t >> 2) & 7;
				m_pos -= *ip++ << 3;
				t = (t >> 5) - 1;

				goto copy_match;

			}
			else if (t >= 32)
			{
				t &= 31;
				if (t == 0) {
					while (*ip == 0) {
						t += 255;
						ip++;
					}
					t += 31 + *ip++;
				}

				m_pos = op - 1;
				m_pos -= (*(unsigned short *)ip) >> 2;
				ip += 2;
			}
			else if (t >= 16) {
				m_pos = op;
				m_pos -= (t & 8) << 11;
				t &= 7;
				if (t == 0) {
					while (*ip == 0) {
						t += 255;
						ip++;
					}
					t += 7 + *ip++;
				}
				m_pos -= (*(unsigned short *)ip) >> 2;
				ip += 2;
				if (m_pos == op)
					goto eof_found;
				m_pos -= 0x4000;
			}
			else {
				m_pos = op - 1;
				m_pos -= t >> 2;
				m_pos -= *ip++ << 2;
				*op++ = *m_pos++; *op++ = *m_pos;
				goto match_done;
			}

			if (t >= 6 && (op - m_pos) >= 4) {
				*(unsigned *)op = *(unsigned *)m_pos;
				op += 4; m_pos += 4; t -= 2;
				do {
					*(unsigned *)op = *(unsigned *)m_pos;
					op += 4; m_pos += 4; t -= 4;
				} while (t >= 4);
				if (t > 0) do *op++ = *m_pos++; while (--t > 0);
			}
			else {
			copy_match:
				*op++ = *m_pos++; *op++ = *m_pos++;
				do *op++ = *m_pos++; while (--t > 0);
			}

		match_done:

			t = ip[-2] & 3;
			if (t == 0)	break;

		match_next:
			do *op++ = *ip++; while (--t > 0);
			t = *ip++;
		}
	}

eof_found:;
}

